BL31: Program Priority Mask for SMC handling
authorJeenu Viswambharan <[email protected]>
Wed, 4 Oct 2017 11:21:34 +0000 (12:21 +0100)
committerJeenu Viswambharan <[email protected]>
Mon, 13 Nov 2017 07:49:30 +0000 (07:49 +0000)
On GICv3 systems, as a side effect of adding provision to handle EL3
interrupts (unconditionally routing FIQs to EL3), pending Non-secure
interrupts (signalled as FIQs) may preempt execution in lower Secure ELs
[1]. This will inadvertently disrupt the semantics of Fast SMC
(previously called Atomic SMC) calls.

To retain semantics of Fast SMCs, the GIC PMR must be programmed to
prevent Non-secure interrupts from preempting Secure execution. To that
effect, two new functions in the Exception Handling Framework subscribe
to events introduced in an earlier commit:

  - Upon 'cm_exited_normal_world', the Non-secure PMR is stashed, and
    the PMR is programmed to the highest Non-secure interrupt priority.

  - Upon 'cm_entering_normal_world', the previously stashed Non-secure
    PMR is restored.

The above sequence however prevents Yielding SMCs from being preempted
by Non-secure interrupts as intended. To facilitate this, the public API
exc_allow_ns_preemption() is introduced that programs the PMR to the
original Non-secure PMR value. Another API
exc_is_ns_preemption_allowed() is also introduced to check if
exc_allow_ns_preemption() had been called previously.

API documentation to follow.

[1] On GICv2 systems, this isn't a problem as, unlike GICv3, pending NS
    IRQs during Secure execution are signalled as IRQs, which aren't
    routed to EL3.

Change-Id: Ief96b162b0067179b1012332cd991ee1b3051dd0
Signed-off-by: Jeenu Viswambharan <[email protected]>
bl31/ehf.c
include/bl31/ehf.h
include/drivers/arm/gic_common.h

index 9758d1aab5bd52a70b3f1bd1aea2c5d3e3e27e0a..65f2df5235fdfbec4d1d6b2d54b754850926a217 100644 (file)
 #include <cpu_data.h>
 #include <debug.h>
 #include <ehf.h>
+#include <gic_common.h>
 #include <interrupt_mgmt.h>
 #include <platform.h>
+#include <pubsub_events.h>
 
 /* Output EHF logs as verbose */
 #define EHF_LOG(...)   VERBOSE("EHF: " __VA_ARGS__)
@@ -208,6 +210,165 @@ void ehf_deactivate_priority(unsigned int priority)
        EHF_LOG("deactivate prio=%d\n", get_pe_highest_active_idx(pe_data));
 }
 
+/*
+ * After leaving Non-secure world, stash current Non-secure Priority Mask, and
+ * set Priority Mask to the highest Non-secure priority so that Non-secure
+ * interrupts cannot preempt Secure execution.
+ *
+ * If the current running priority is in the secure range, or if there are
+ * outstanding priority activations, this function does nothing.
+ *
+ * This function subscribes to the 'cm_exited_normal_world' event published by
+ * the Context Management Library.
+ */
+static void *ehf_exited_normal_world(const void *arg)
+{
+       unsigned int run_pri;
+       pe_exc_data_t *pe_data = this_cpu_data();
+
+       /* If the running priority is in the secure range, do nothing */
+       run_pri = plat_ic_get_running_priority();
+       if (IS_PRI_SECURE(run_pri))
+               return 0;
+
+       /* Do nothing if there are explicit activations */
+       if (has_valid_pri_activations(pe_data))
+               return 0;
+
+       assert(pe_data->ns_pri_mask == 0);
+
+       pe_data->ns_pri_mask =
+               plat_ic_set_priority_mask(GIC_HIGHEST_NS_PRIORITY);
+
+       /* The previous Priority Mask is not expected to be in secure range */
+       if (IS_PRI_SECURE(pe_data->ns_pri_mask)) {
+               ERROR("Priority Mask (0x%x) already in secure range\n",
+                               pe_data->ns_pri_mask);
+               panic();
+       }
+
+       EHF_LOG("Priority Mask: 0x%x => 0x%x\n", pe_data->ns_pri_mask,
+                       GIC_HIGHEST_NS_PRIORITY);
+
+       return 0;
+}
+
+/*
+ * Conclude Secure execution and prepare for return to Non-secure world. Restore
+ * the Non-secure Priority Mask previously stashed upon leaving Non-secure
+ * world.
+ *
+ * If there the current running priority is in the secure range, or if there are
+ * outstanding priority activations, this function does nothing.
+ *
+ * This function subscribes to the 'cm_entering_normal_world' event published by
+ * the Context Management Library.
+ */
+static void *ehf_entering_normal_world(const void *arg)
+{
+       unsigned int old_pmr, run_pri;
+       pe_exc_data_t *pe_data = this_cpu_data();
+
+       /* If the running priority is in the secure range, do nothing */
+       run_pri = plat_ic_get_running_priority();
+       if (IS_PRI_SECURE(run_pri))
+               return 0;
+
+       /*
+        * If there are explicit activations, do nothing. The Priority Mask will
+        * be restored upon the last deactivation.
+        */
+       if (has_valid_pri_activations(pe_data))
+               return 0;
+
+       /* Do nothing if we don't have a valid Priority Mask to restore */
+       if (pe_data->ns_pri_mask == 0)
+               return 0;
+
+       old_pmr = plat_ic_set_priority_mask(pe_data->ns_pri_mask);
+
+       /*
+        * When exiting secure world, the current Priority Mask must be
+        * GIC_HIGHEST_NS_PRIORITY (as set during entry), or the Non-secure
+        * priority mask set upon calling ehf_allow_ns_preemption()
+        */
+       if ((old_pmr != GIC_HIGHEST_NS_PRIORITY) &&
+                       (old_pmr != pe_data->ns_pri_mask)) {
+               ERROR("Invalid Priority Mask (0x%x) restored\n", old_pmr);
+               panic();
+       }
+
+       EHF_LOG("Priority Mask: 0x%x => 0x%x\n", old_pmr, pe_data->ns_pri_mask);
+
+       pe_data->ns_pri_mask = 0;
+
+       return 0;
+}
+
+/*
+ * Program Priority Mask to the original Non-secure priority such that
+ * Non-secure interrupts may preempt Secure execution, viz. during Yielding SMC
+ * calls.
+ *
+ * This API is expected to be invoked before delegating a yielding SMC to Secure
+ * EL1. I.e. within the window of secure execution after Non-secure context is
+ * saved (after entry into EL3) and Secure context is restored (before entering
+ * Secure EL1).
+ */
+void ehf_allow_ns_preemption(void)
+{
+       unsigned int old_pmr __unused;
+       pe_exc_data_t *pe_data = this_cpu_data();
+
+       /*
+        * We should have been notified earlier of entering secure world, and
+        * therefore have stashed the Non-secure priority mask.
+        */
+       assert(pe_data->ns_pri_mask != 0);
+
+       /* Make sure no priority levels are active when requesting this */
+       if (has_valid_pri_activations(pe_data)) {
+               ERROR("PE %lx has priority activations: 0x%x\n",
+                               read_mpidr_el1(), pe_data->active_pri_bits);
+               panic();
+       }
+
+       old_pmr = plat_ic_set_priority_mask(pe_data->ns_pri_mask);
+
+       EHF_LOG("Priority Mask: 0x%x => 0x%x\n", old_pmr, pe_data->ns_pri_mask);
+
+       pe_data->ns_pri_mask = 0;
+}
+
+/*
+ * Return whether Secure execution has explicitly allowed Non-secure interrupts
+ * to preempt itself, viz. during Yielding SMC calls.
+ */
+unsigned int ehf_is_ns_preemption_allowed(void)
+{
+       unsigned int run_pri;
+       pe_exc_data_t *pe_data = this_cpu_data();
+
+       /* If running priority is in secure range, return false */
+       run_pri = plat_ic_get_running_priority();
+       if (IS_PRI_SECURE(run_pri))
+               return 0;
+
+       /*
+        * If Non-secure preemption was permitted by calling
+        * ehf_allow_ns_preemption() earlier:
+        *
+        * - There wouldn't have been priority activations;
+        * - We would have cleared the stashed the Non-secure Priority Mask.
+        */
+       if (has_valid_pri_activations(pe_data))
+               return 0;
+       if (pe_data->ns_pri_mask != 0)
+               return 0;
+
+       return 1;
+}
+
 /*
  * Top-level EL3 interrupt handler.
  */
@@ -338,3 +499,6 @@ void ehf_register_priority_handler(unsigned int pri, ehf_handler_t handler)
 
        EHF_LOG("register pri=0x%x handler=%p\n", pri, handler);
 }
+
+SUBSCRIBE_TO_EVENT(cm_entering_normal_world, ehf_entering_normal_world);
+SUBSCRIBE_TO_EVENT(cm_exited_normal_world, ehf_exited_normal_world);
index 142b4c0aa343325b48dd95ed6c0e4ea746f4f285..be8c957cc9b3ee46aa5d8a85f655bba997f176f7 100644 (file)
@@ -55,6 +55,9 @@ typedef struct {
 
        /* Priority mask value before any priority levels were active */
        uint8_t init_pri_mask;
+
+       /* Non-secure priority mask value stashed during Secure execution */
+       uint8_t ns_pri_mask;
 } __aligned(sizeof(uint64_t)) pe_exc_data_t;
 
 typedef int (*ehf_handler_t)(uint32_t intr_raw, uint32_t flags, void *handle,
@@ -79,6 +82,8 @@ void ehf_init(void);
 void ehf_activate_priority(unsigned int priority);
 void ehf_deactivate_priority(unsigned int priority);
 void ehf_register_priority_handler(unsigned int pri, ehf_handler_t handler);
+void ehf_allow_ns_preemption(void);
+unsigned int ehf_is_ns_preemption_allowed(void);
 
 #endif /* __ASSEMBLY__ */
 
index 9e126a854b41e1c3630f0c8bb06b5d43b518fd40..efa9703e6da6cb318207673b06cd660d3cb57938 100644 (file)
 #define GIC_INTR_CFG_EDGE              1
 
 /* Constants to categorise priorities */
-#define GIC_HIGHEST_SEC_PRIORITY       0
-#define GIC_LOWEST_SEC_PRIORITY                127
-#define GIC_HIGHEST_NS_PRIORITY                128
-#define GIC_LOWEST_NS_PRIORITY         254 /* 255 would disable an interrupt */
+#define GIC_HIGHEST_SEC_PRIORITY       0x0
+#define GIC_LOWEST_SEC_PRIORITY                0x7f
+#define GIC_HIGHEST_NS_PRIORITY                0x80
+#define GIC_LOWEST_NS_PRIORITY         0xfe /* 0xff would disable all interrupts */
 
 /*******************************************************************************
  * GIC Distributor interface register offsets that are common to GICv3 & GICv2